/**
 * 
 */
package de.guidoludwig.jtm.server;

import java.util.List;
import java.util.StringTokenizer;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.apache.lucene.analysis.KeywordAnalyzer;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.queryParser.QueryParser.Operator;
import org.apache.lucene.util.Version;
import org.hibernate.search.SearchException;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.query.dsl.QueryBuilder;

import de.guidoludwig.jtm.SearchModel;
import de.guidoludwig.jtm.domain.AbstractPersistentObject;
import de.guidoludwig.jtm.util.StringUtil;

/**
 * @author guido
 * 
 */
public abstract class AbstractRepository<T extends AbstractPersistentObject> {

	private final Class<T> rootClass;

	protected AbstractRepository(Class<T> rootClass) throws Exception {
		this.rootClass = rootClass;
	}

	public final T findByOID(String oid) {
		EntityManager entityManager = Server.getInstance()
				.createEntityManager();
		entityManager.getTransaction().begin();
		T stored = findByOID(oid, entityManager);
		entityManager.getTransaction().commit();
		entityManager.close();
		return stored;
	}

	private final T findByOID(String oid, EntityManager em) {
		T stored = em.find(rootClass, oid);
		return stored;
	}

	public final void store(T entity) {
		EntityManager entityManager = Server.getInstance()
				.createEntityManager();
		entityManager.getTransaction().begin();
		entityManager.persist(entity);
		entityManager.getTransaction().commit();
		entityManager.close();
	}

	public final void remove(T entity) {
		EntityManager entityManager = Server.getInstance()
				.createEntityManager();
		entityManager.getTransaction().begin();
		T eStored = findByOID(entity.getOid(), entityManager);
		if (eStored != null) {
			entityManager.remove(eStored);
		}
		entityManager.getTransaction().commit();
		entityManager.close();
	}

	public List<T> findIntelli(SearchModel searchModel) throws SearchException {
		String[] searchFields = getSearchFields();
		if (searchFields.length == 0) {
			throw new IllegalStateException("No searchfields");
		}
		EntityManager em = Server.getInstance().createEntityManager();
		FullTextEntityManager fullTextEntityManager = Search
				.getFullTextEntityManager(em);
		em.getTransaction().begin();

		// create native Lucene query unsing the query DSL
		// alternatively you can write the Lucene query using the Lucene query
		// parser
		// or the Lucene programmatic API. The Hibernate Search DSL is
		// recommended though

		QueryBuilder qb = fullTextEntityManager.getSearchFactory()
				.buildQueryBuilder().forEntity(rootClass).get();

		String searchString = searchModel.getCriteria();
		org.apache.lucene.search.Query query;
		if (StringUtil.isBlank(searchString)) {
			query = qb.keyword().wildcard().onField(getSearchFields()[0])
					.matching("*").createQuery();
		} else {
			query = createLuceneQuery(getSearchFields(), searchString);
		}

		System.out.println(query.toString());
		
		// wrap Lucene query in a javax.persistence.Query
		javax.persistence.Query persistenceQuery = fullTextEntityManager
				.createFullTextQuery(query, rootClass);

		// execute search
		List<T> result = persistenceQuery.getResultList();
		em.getTransaction().commit();
		em.close();
		System.out.println("Found : " + result.size());
		return result; 
	}

	protected abstract String[] getSearchFields();

	@SuppressWarnings("unchecked")
	public List<T> loadAll() {
		EntityManager em = Server.getInstance().createEntityManager();
		Query q = em.createQuery("from " + rootClass.getCanonicalName());
		List<T> result = q.getResultList();
		em.close();
		return result;
	}

	private org.apache.lucene.search.Query createLuceneQuery(
			String[] searchFields, String searchCriteria) {
		QueryParser luceneParser;

		String sc = searchCriteria == null ? "" : searchCriteria.toLowerCase();
		if (StringUtil.isBlank(sc)) {
			luceneParser = new QueryParser(Version.LUCENE_35, "",
					new KeywordAnalyzer());
		} else {
			StringTokenizer t = new StringTokenizer(searchCriteria);
			StringBuilder wc = new StringBuilder();
			while (t.hasMoreElements()) {
				String token  = (String) t.nextElement();
				wc.append(token + "* ");
			}
			sc = wc.toString().trim();
			MultiFieldQueryParser mfqp = new MultiFieldQueryParser(Version.LUCENE_35,
					searchFields, new KeywordAnalyzer());
			mfqp.setDefaultOperator(Operator.AND);
			luceneParser = mfqp;
		}
		StringBuilder builder = new StringBuilder(sc.length());
		builder.append(sc);
		try {
			return luceneParser.parse(builder.toString());
		} catch (ParseException pe) {
			System.out.println(pe);
			return null;
		}
	}
	
}
